﻿/*=========================================================================
 * AUTH: Vincent Breton
 * DESC: Teammate Class
 *=========================================================================*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;   //   for Texture2D
using Microsoft.Xna.Framework;  //  for Vector2
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace RisingThreat
{
    class Teammate
    {
        public Steering steer { get; set; }
        private Model teammate;
        public SteeringArrive seek { get; set; }
        //public WallAvoidance wall { get; set; }
        public Align align { get; set; }
        private TeammateStateMachine stateMachine;
        public bool isAlive;
        public bool isShooting;
        public Bullet[] bullets = new Bullet[1];
        public Vector3 shootingTarget;
        //public CollisionHandler handler;
        public PathFollowing path;

        public BoundingSphere bounding;


        public Teammate(Model t, Vector3 position, LevelGraph g, Model b)
        {
            teammate = t;
            steer = new Steering(position, new Vector3(0.0f, 0.0f, 0.0f), new Vector3(0f, 0f, 0f), 0, 0, 0, new Vector3(2.5f, 0, 2.5f) * 2,
                new Vector3(5.0f, 0.0f, 5.0f) * 2, 60.0f, 60.0f);
            seek = new SteeringArrive();
            //wall = new WallAvoidance();
            align = new Align();
            stateMachine = new TeammateStateMachine(this);
            isAlive = true;

           // handler = new CollisionHandler(steer, CollisionHandler.Type.TEAMMATE);
            path = new PathFollowing(g);

            for (int i = 0; i != 1; i++)
            {
                bullets[i] = new Bullet(b);
            }

            bounding.Center = steer.position;
            bounding.Radius = 0.5f; //random
        }

        public void update(GameTime elapsedTime, Level level)
        {
            if (isAlive)
            {
                stateMachine.update();
                move(elapsedTime);
                bounding.Center = steer.position;
                bounding.Center.Y = 1.0f;
                CheckForCollisions(level);
                steer.position.Y = 0.0f;//Y value clamp
                SearchAndShoot(elapsedTime);
                if (steer.health <= 0)
                    isAlive = false;
            }
            foreach (Bullet bullet in bullets)
            {
                if (bullet.isAlive)
                {
                    bullet.Update(elapsedTime, level);
                }
            }
        }

        public void SearchAndShoot(GameTime elapsedTime)
        {
            if (GameScreen.enemies.Count != 0)
            {
                Enemy closest = GameScreen.enemies[0];
                foreach (Enemy enemy in GameScreen.enemies)
                {
                    if ((enemy.steer.position - steer.position).Length() < (closest.steer.position - steer.position).Length() && enemy.isAlive)
                    {
                        closest = enemy;
                    }
                }
                if ((closest.steer.position - steer.position).Length() < 8.0f )
                {
                    Vector3 dir = (closest.steer.position - steer.position);
                    dir.Normalize();
                    shootingTarget = dir;
                    isShooting = true;
                    steer = align.getSteeringToTarget(steer, shootingTarget);
                    if (steer.rotation == 0) //align first before shooting
                    {
                        Shoot(closest);
                        isShooting = false;
                    }
                }
                else
                    isShooting = false;
            }                
        }

        public void Shoot(Enemy target)
        {
            foreach (Bullet bullet in bullets)
            {
                if (!bullet.isAlive)
                {
                    bullet.isAlive = true;
                    Vector3 dir = (target.steer.position - steer.position);
                    dir.Normalize();
                    shootingTarget = dir;
                    // bullet.steer.position+=Vector3.Up; // make bullet alligned with gun
                    bullet.velocity = dir * bullet.speed;
                    bullet.velocity.Y = 0.0f;
                    bullet.position = steer.position;
                    bullet.position.Y = 1.0f;
                    bullet.bounding.Center.Y = 1.0f;
                    bullet.orientation = (steer.orientation);
                    //bullet.orientation = (float)Math.Atan2(dir.X, dir.Z);
                    break;
                }
            }
        }

        public void CheckForCollisions(Level level)
        {
            CollisionDetector cd = new CollisionDetector();
            List<QuadTreeItem> collisions = cd.detectLevelCollisions(bounding, level.getPotentialCollisions(bounding));
            foreach (QuadTreeItem item in collisions)
            {
                if(bounding.Intersects(item.getBoundingVolume()))
                {
                    ResolveCollisionBVH(item);
                }
            }
        }

        public void ResolveCollisionBVH(QuadTreeItem item)
        {
            BoundingBox itemBound = new BoundingBox(item.getBoundingVolume().Min, item.getBoundingVolume().Max);
            Vector3 itemBoundCenter = new Vector3((itemBound.Min.X + itemBound.Max.X) / 2, 1.5f, (itemBound.Min.Z + itemBound.Max.Z) / 2);

            if ((this.steer.position.X>(itemBound.Min.X-this.bounding.Radius/2))&&this.steer.position.X<(itemBound.Max.X+this.bounding.Radius/2))
            {
                if (this.steer.position.Z > itemBoundCenter.Z)
                {
                    Vector3 contactNorm = new Vector3(0, 0, -1);
                    contactNorm.Normalize();
                    //contact position is implied with intersecting spheres
                    //calculate seperation velocity
                    Vector3 seperationVelocity = (this.steer.velocity);
                    seperationVelocity = seperationVelocity * contactNorm;
                    //use a coefficient of impulse
                    seperationVelocity *= 1.5f;
                    //apply impulse
                    this.steer.velocity += seperationVelocity;
                    //added code to make things smoother; so my code uses projection as well as impulses.
                    this.steer.position.Z = (itemBound.Max.Z + this.bounding.Radius);
                }
                else
                {
                    Vector3 contactNorm = new Vector3(0, 0, -1);
                    contactNorm.Normalize();
                    //contact position is implied with intersecting spheres
                    //calculate seperation velocity
                    Vector3 seperationVelocity = (this.steer.velocity);
                    seperationVelocity = seperationVelocity * contactNorm;
                    //use a coefficient of impulse
                    seperationVelocity *= 1.5f;
                    //apply impulse
                    this.steer.velocity += seperationVelocity;
                    //added code to make things smoother; so my code uses projection as well as impulses.
                    this.steer.position.Z = (itemBound.Min.Z - this.bounding.Radius);
                }
            }
            else
            {
                if (this.steer.position.X > itemBoundCenter.X)
                {
                    Vector3 contactNorm = new Vector3(-1, 0, 0);
                    contactNorm.Normalize();
                    //contact position is implied with intersecting spheres
                    //calculate seperation velocity
                    Vector3 seperationVelocity = (this.steer.velocity);
                    seperationVelocity = seperationVelocity * contactNorm;
                    //use a coefficient of impulse
                    seperationVelocity *= 1.5f;
                    //apply impulse
                    this.steer.velocity += seperationVelocity;
                    //added code to make things smoother; so my code uses projection as well as impulses.
                    this.steer.position.X = (itemBound.Max.X + this.bounding.Radius);
                }
                else
                {
                    Vector3 contactNorm = new Vector3(-1, 0, 0);
                    contactNorm.Normalize();
                    //contact position is implied with intersecting spheres
                    //calculate seperation velocity
                    Vector3 seperationVelocity = (this.steer.velocity);
                    seperationVelocity = seperationVelocity * contactNorm;
                    //use a coefficient of impulse
                    seperationVelocity *= 1.5f;
                    //apply impulse
                    this.steer.velocity += seperationVelocity;
                    //added code to make things smoother; so my code uses projection as well as impulses.
                    this.steer.position.X = (itemBound.Min.X - this.bounding.Radius);
                }
            }        
        }

       

        public void move(GameTime elapsedTime)
        {
            float timeDifference = (float)elapsedTime.ElapsedGameTime.TotalMilliseconds / 1000.0f;

            if (stateMachine.automaticON)
                moveAutomatic(timeDifference);
            else
                moveTactical(timeDifference);            
        }

        public void moveToTarget(GameTime elapsedTime, Vector3 target)
        {
            float timeDifference = (float)elapsedTime.ElapsedGameTime.TotalMilliseconds / 1000.0f;

            if (stateMachine.automaticON)
                moveAutomaticToTarget(timeDifference,target);
            else
                moveTactical(timeDifference);
        }

        // move for tactical state machine
        public void moveTactical(float timeDifference)
        {
            return;
        }
        
        // standard move for automatic state machine: follows formation and shoots
        public void moveAutomatic(float timeDifference)
        {
            //it will shoot and move towards its formation slot position
            if (stateMachine.currentState == TeammateStateMachine.State.FOLLOW)
            {
                fire();
                steer = seek.getSteering(steer);
                updatePosition(timeDifference);
                //will need to be changed so that it faces where he is shooting instead of where he is going
                if ((seek.target - steer.position).Length() > 0.5f)
                {
                    if (isShooting)
                        steer = align.getSteeringToTarget(steer, shootingTarget);
                    else
                        steer = align.getSteering(steer);
                    
                }
               
                    
                updateOrientation(timeDifference);
            }

            else //if (stateMachine.currentState == TeammateStateMachine.State.CATCHUP)//currentState==CATCHUP: it stops shooting and moves towards slot position
            {
                steer = seek.getSteering(steer);
                updatePosition(timeDifference);
                //will need to be changed so that it faces where he is shooting instead of where he is going
                steer = align.getSteering(steer);
                updateOrientation(timeDifference);
            }
        }

        public void moveAutomaticToTarget(float timeDifference, Vector3 target)
        {
            //it will shoot and move towards its formation slot position
            if (stateMachine.currentState == TeammateStateMachine.State.FOLLOW)
            {
                fire();
                steer = seek.getSteering(steer);
                updatePosition(timeDifference);
                //will need to be changed so that it faces where he is shooting instead of where he is going
                if ((seek.target - steer.position).Length() > 1.1f)
                {
                    steer = align.getSteeringToTarget(steer,target);
                }
                updateOrientation(timeDifference);

            }

            else //if (stateMachine.currentState == TeammateStateMachine.State.CATCHUP)//currentState==CATCHUP: it stops shooting and moves towards slot position
            {
                steer = seek.getSteering(steer);
                updatePosition(timeDifference);
                //will need to be changed so that it faces where he is shooting instead of where he is going
                steer = align.getSteering(steer);
                updateOrientation(timeDifference);
            }
        }


        public void updatePosition(float timeGame)
        {
            steer.velocity += steer.acceleration * timeGame;
            if (steer.velocity.Length() > steer.maxVelocity.Length())
            {
                Vector3 temp = steer.velocity;
                temp.Normalize();
                steer.velocity = temp;
                steer.velocity *= steer.maxVelocity;
                
            }

            steer.position += steer.velocity * timeGame;

        }

        public void updateOrientation(float timeGame)
        {
            steer.rotation += steer.angularAcceleration;
            if (steer.rotation > steer.maxRotation)
            {
                steer.rotation /= steer.rotation;
                steer.rotation *= steer.maxRotation;
            }

            steer.orientation += steer.rotation * timeGame;
        }
        /// <summary>
        /// method that takes care of teammates shooting mechanics
        /// </summary>
        private void fire()
        {
            Vector3 target = findTargetToShoot();
            
        }
        //it will see if any targets are nearby and set it as its shooting target
        public Vector3 findTargetToShoot()
        {
            return new Vector3(0.0f, 0.0f, 0.0f);
        }

        public void Draw(GraphicsDeviceManager graphics, Matrix view, Matrix projection)
        {
            if (isAlive)
            {
                // DRAW teammate
                // Copy any parent transforms.
                Matrix[] transforms = new Matrix[teammate.Bones.Count];
                teammate.CopyAbsoluteBoneTransformsTo(transforms);

                // Draw the model. A model can have multiple meshes, so loop.


                foreach (ModelMesh mesh in teammate.Meshes)
                {


                    // This is where the mesh orientation is set, as well 
                    // as our camera and projection.
                    foreach (BasicEffect effect in mesh.Effects)
                    {
                        effect.EnableDefaultLighting();
                        float scaleAmount = 0.03f;

                        effect.World = transforms[mesh.ParentBone.Index] * Matrix.CreateScale(scaleAmount) * Matrix.CreateRotationY(MathHelper.ToRadians(steer.orientation)) * Matrix.CreateTranslation(new Vector3(steer.position.X, 0.2f, steer.position.Z));
                        effect.View = view;
                        effect.Projection = projection;
                    }
                    // Draw the mesh, using the effects set above.
                    mesh.Draw();
                }
            }
        }


    }
}
